package com.fly.docker.web;

import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fly.core.entity.JsonResult;
import com.fly.docker.service.DockerService;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.spotify.docker.client.DefaultDockerClient;
import com.spotify.docker.client.DockerCertificates;
import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.DockerClient.ListContainersParam;
import com.spotify.docker.client.DockerClient.ListImagesParam;
import com.spotify.docker.client.DockerClient.LogsParam;
import com.spotify.docker.client.LogStream;
import com.spotify.docker.client.exceptions.DockerCertificateException;
import com.spotify.docker.client.exceptions.DockerException;
import com.spotify.docker.client.messages.Container;
import com.spotify.docker.client.messages.ContainerConfig;
import com.spotify.docker.client.messages.ContainerCreation;
import com.spotify.docker.client.messages.ContainerInfo;
import com.spotify.docker.client.messages.HostConfig;
import com.spotify.docker.client.messages.Image;
import com.spotify.docker.client.messages.PortBinding;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("/docker/rest")
@Api(tags = "docker rest api接口")
public class DockerRestApi
{
    @Autowired
    DockerService dockerService;
    
    @ApiOperation("001-DockerClient初始化")
    @ApiOperationSupport(order = 10)
    @PostMapping("/init")
    @Valid
    public JsonResult<?> init(DockerClientConfig config)
        throws DockerException, InterruptedException, DockerCertificateException
    {
        // TLS客户端证书，注意：/etc/docker/为容器内存在的客户端证书、私钥文件目录
        // 需要提前做映射，e.g: - /etc/docker/:/etc/docker/
        DockerClient dockerClient = null;
        if (SystemUtils.IS_OS_UNIX)
        {
            dockerClient = DefaultDockerClient.builder().uri(URI.create("https://118.178.86.130:2375")).dockerCertificates(new DockerCertificates(Paths.get("/etc/docker/"))).build();
        }
        else if (SystemUtils.IS_OS_WINDOWS)
        {
            dockerClient = DefaultDockerClient.builder().uri(URI.create("https://118.178.86.130:2375")).dockerCertificates(new DockerCertificates(Paths.get("/docker_ca/"))).build();
        }
        if (StringUtils.equals("OK", dockerClient.ping()))
        {
            Map<String, DockerClient> dockerclients = dockerService.addNew(dockerClient);
            return JsonResult.success(dockerclients, "DockerClient初始化成功！");
        }
        return JsonResult.error("DockerClient初始化失败！");
    }
    
    @ApiOperation("0011-DockerClient资源池")
    @ApiOperationSupport(order = 11)
    @GetMapping("/clients")
    public JsonResult<?> clients()
    {
        return JsonResult.success(dockerService.getClients(), "处理成功！");
    }
    
    @ApiOperation("002-自动选择活跃节点启动容器")
    @ApiOperationSupport(order = 20)
    @PostMapping("/startContainer")
    public JsonResult<?> startContainer(@RequestBody @Valid ContainerBase container)
        throws Exception
    {
        DockerClient dockerClient = dockerService.invokeAny();
        
        // 拉取镜像
        dockerClient.pull(container.getImage());
        
        // 处理端口映射
        Map<String, List<PortBinding>> portBinds = new HashMap<>();
        Map<String, String> ports = container.getPorts();
        for (String hostPort : ports.keySet())
        {
            List<PortBinding> hostPorts = new ArrayList<>();
            hostPorts.add(PortBinding.of("0.0.0.0", hostPort));
            portBinds.put(ports.get(hostPort), hostPorts);
        }
        
        ContainerConfig containerConfig = ContainerConfig.builder()
            .hostname(container.getHostname())
            .domainname(container.getDomainname())
            .user(container.getUser())
            .attachStdin(container.getAttachStdin())
            .attachStdout(container.getAttachStdout())
            .attachStderr(container.getAttachStderr())
            .tty(container.getTty())
            .openStdin(container.getOpenStdin())
            .stdinOnce(container.getStdinOnce())
            .image(container.getImage()) // 镜像
            .workingDir(container.getWorkingDir())
            .networkDisabled(false)
            .macAddress(container.getMacAddress())
            .stopSignal(container.getStopSignal())
            .volumes(container.getVolumes())
            .portSpecs(container.getPortSpecs())
            .exposedPorts(container.getExposedPorts())// 暴露端口
            .env(container.getEnv())
            .cmd(container.getCmd())
            .entrypoint(container.getEntrypoint())
            .onBuild(container.getOnBuild())
            .labels(container.getLabels())
            .hostConfig(HostConfig.builder()
                .portBindings(portBinds)// 添加端口映射
                .networkMode(container.getNetwork()) // 设置网络模式
                .build())
            // .healthcheck(container.getHealthcheck())
            // .networkingConfig(container.getNetworkingConfig())
            .build();
        String containerName = "docker-client-show-" + RandomStringUtils.random(8);
        ContainerCreation creation = dockerClient.createContainer(containerConfig, containerName);
        dockerClient.startContainer(creation.id());
        printContainLog(dockerClient, creation.id());
        return JsonResult.success(Collections.singletonMap("containerId", creation.id())).setMessage("启动容器成功！");
    }
    
    @ApiOperation("003-运行容器列表")
    @ApiOperationSupport(order = 30)
    @ApiImplicitParam(name = "index", value = "客户端下标，起始值0", required = true)
    @GetMapping("/{index}/listContainers")
    public JsonResult<?> listContainers(@PathVariable int index)
        throws DockerException, InterruptedException
    {
        DockerClient dockerClient = dockerService.choose(index);
        List<Container> containers = dockerClient.listContainers();
        return JsonResult.success(Collections.singletonMap("containers", containers));
    }
    
    @ApiOperation("0031-全部状态容器列表")
    @ApiOperationSupport(order = 32)
    @ApiImplicitParam(name = "index", value = "客户端下标，起始值0", required = true)
    @GetMapping("/{index}/listAllContainers")
    public JsonResult<?> listAllContainers(@PathVariable int index)
        throws DockerException, InterruptedException
    {
        DockerClient dockerClient = dockerService.choose(index);
        List<Container> containers = dockerClient.listContainers(ListContainersParam.allContainers());
        return JsonResult.success(Collections.singletonMap("containers", containers));
    }
    
    @ApiOperation("004-容器详情")
    @ApiOperationSupport(order = 40)
    @ApiImplicitParam(name = "index", value = "客户端下标，起始值0", required = true)
    @GetMapping("/{index}/inspectContainer/{containerId}")
    public JsonResult<?> inspectContainer(@PathVariable int index, @PathVariable String containerId)
        throws DockerException, InterruptedException
    {
        DockerClient dockerClient = dockerService.choose(index);
        ContainerInfo info = dockerClient.inspectContainer(containerId);
        return JsonResult.success(Collections.singletonMap("container", info));
    }
    
    @ApiOperation("005-停止容器")
    @ApiOperationSupport(order = 50)
    @ApiImplicitParam(name = "index", value = "客户端下标，起始值0", required = true)
    @PostMapping("/{index}/stopContainer/{containerId}")
    public JsonResult<?> stopContainer(@PathVariable int index, @PathVariable String containerId)
        throws DockerException, InterruptedException
    {
        DockerClient dockerClient = dockerService.choose(index);
        ContainerInfo info = dockerClient.inspectContainer(containerId);
        if (info.state().running())
        {
            dockerClient.stopContainer(containerId, 2);
            printContainLog(dockerClient, containerId);
        }
        return JsonResult.success("停止容器成功");
    }
    
    @ApiOperation("006-删除容器")
    @ApiOperationSupport(order = 60)
    @ApiImplicitParam(name = "index", value = "客户端下标，起始值0", required = true)
    @PostMapping("/{index}/removeContainer/{containerId}")
    public JsonResult<?> removeContainer(@PathVariable int index, @PathVariable String containerId)
        throws DockerException, InterruptedException
    {
        DockerClient dockerClient = dockerService.choose(index);
        ContainerInfo info = dockerClient.inspectContainer(containerId);
        if (info.state().running())
        {
            dockerClient.stopContainer(containerId, 2);
            printContainLog(dockerClient, containerId);
        }
        dockerClient.removeContainer(containerId);
        return JsonResult.success("删除容器成功");
    }
    
    @ApiOperation("007-强制杀死容器")
    @ApiOperationSupport(order = 70)
    @ApiImplicitParam(name = "index", value = "客户端下标，起始值0", required = true)
    @PostMapping("/{index}/killContainer/{containerId}")
    public JsonResult<?> killContainer(@PathVariable int index, @PathVariable String containerId)
        throws DockerException, InterruptedException
    {
        DockerClient dockerClient = dockerService.choose(index);
        ContainerInfo info = dockerClient.inspectContainer(containerId);
        if (info.state().running())
        {
            dockerClient.killContainer(containerId);
            printContainLog(dockerClient, containerId);
        }
        dockerClient.removeContainer(containerId);
        return JsonResult.success("强制杀死容器成功");
    }
    
    /**
     * 注意高版本docker可能不支持
     * 
     * @param imageName
     * @return
     * @throws DockerException
     * @throws InterruptedException
     */
    @ApiOperation("008-查看镜像列表")
    @ApiOperationSupport(order = 80)
    @ApiImplicitParam(name = "index", value = "客户端下标，起始值0", required = true)
    @GetMapping("/{index}/listImages")
    public JsonResult<?> listImages(@PathVariable int index)
        throws DockerException, InterruptedException
    {
        DockerClient dockerClient = dockerService.choose(index);
        List<Image> images = dockerClient.listImages(ListImagesParam.allImages(false));
        
        // 只支持全名，如：registry.cn-shanghai.aliyuncs.com/00fly/springboot-hello
        // 或带全名版本号，如：registry.cn-shanghai.aliyuncs.com/00fly/springboot-hello:1.0.0
        // List<Image> images = dockerClient.listImages(ListImagesParam.byName(imageName));
        return JsonResult.success(Collections.singletonMap("images", images));
    }
    
    /**
     * 打印日志
     * 
     * @param containerId
     * @throws DockerException
     * @throws InterruptedException
     */
    private void printContainLog(DockerClient dockerClient, String containerId)
        throws DockerException, InterruptedException
    {
        try (LogStream stream = dockerClient.logs(containerId, LogsParam.stdout(), LogsParam.stderr()))
        {
            String logs = stream.readFully();
            log.info("{}", logs);
        }
    }
}

@Data
class DockerClientConfig
{
    @NotBlank(message = "docker remote地址不能为空")
    @ApiModelProperty(value = "docker remote地址", example = "http://192.168.182.10:2375", position = 0)
    private String dockerServerUrl;
}

@Data
@ApiModel(description = "容器配置信息")
class ContainerBase
{
    @NotBlank(message = "镜像名不能为空")
    @ApiModelProperty(value = "镜像名", example = "registry.cn-shanghai.aliyuncs.com/00fly/docker-client-show:0.0.1", required = true, position = 0)
    private String image;
    
    @ApiModelProperty(value = "网络模式,默认birdge", allowableValues = "birdge,host,none", example = "birdge")
    @Pattern(regexp = "birdge|host|none", message = "网络模式取值birdge,host,none")
    private String network;
    
    private String hostname;
    
    private String domainname;
    
    private String user;
    
    private Boolean attachStdin;
    
    private Boolean attachStdout;
    
    private Boolean attachStderr;
    
    private List<String> portSpecs;
    
    private Set<String> exposedPorts;
    
    private Boolean tty;
    
    private Boolean openStdin;
    
    private Boolean stdinOnce;
    
    private List<String> env;
    
    private List<String> cmd;
    
    private Set<String> volumes;
    
    private String workingDir;
    
    private List<String> entrypoint;
    
    private Boolean networkDisabled = false;
    
    private List<String> onBuild;
    
    private Map<String, String> labels;
    
    private String macAddress;
    
    private String stopSignal;
    
    @ApiModelProperty(value = "映射端口， key：主机port，value：容器端口")
    private Map<String, String> ports;
    
    // private HostConfig hostConfig;
    
    // private Healthcheck healthcheck;
    
    // private NetworkingConfig networkingConfig;
}